#pragma once

#include "object/building.h"
#include "object/hostel.h"
#include "object/student.h"
#include <common.h>
#include <windows.h>
#include <cvector.h>
#include <assert.h>
#include <objects.h>

#define CONSOLE_CMD_SIZE 16
#define INPUT_BUFFER_SIZE 64

#define ECHO_UNKNOWN_OPTION(exe, option) \
    fprintf(stderr, "\"%s\" is an unknown option of %s, show usage in `help`\n", option, exe)

#define ECHO_CMD_REQUIRES_ARG(exe) \
    fprintf(stderr, "\"%s\" require arguments, show usage in `help`\n", exe)

HM_private
size_t hm_atoi_(char const* str) {
    return atoi(str); // bug: num overflow
}

HM_private
void console_main_(int argc, char **argv) {
    assert(argc >= 1);
    char const*const exe = argv[0];

    if (strcmp(exe, "q") == 0 || strcmp(exe, "quit") == 0) {
        _Exit(0);
    }
    else if (strcmp(exe, "h") == 0 || strcmp(exe, "help") == 0) {
        printf(
            "  Explaination:\n"
            "    <option>: enum of \"students\", \"hostels\", \"buildings\"\n"
            "\n\n"
            "  Usage:\n"
            "    q, quit: exit the console\n"
            "    h, help: show this message\n"
            "\n"
            "    register <option> [values ...]: register <option> with [values ...]\n"
            "    *  register students <student_id> <hostel_id> <building_id> <bed_id>\n"
            "    *  register hostels <hostel_id> <building_id> <bed_num>\n"
            "    *  register buildings <building_id> <hostel_num> <is_male>\n"
            "\n"
            "    unregister <option> [value]\n"
            "    *  unregister students <student_id>\n"
            "    *  unregister hostels <hostel_id> <building_id>\n"
            "    *  unregister buildings <building_id>\n"
            "\n"
            "    edit <option> [values ...]: edit <option> with [values ...]\n"
            "    *  edit students <student_id> <hostel_id> <building_id> <bed_id>\n"
            "    *  edit hostels <hostel_id> <building_id> <bed_num>\n"
            "    *  edit buildings <building_id> <hostel_num> <is_male>\n"
            "\n"
            "    list <option>: Print all inside <option>\n"
            "    *  list students  # print all lived student\n"
            "    *  list hostels  # print all used hostels\n"
            "    *  list buildings  # print all buildings\n"
            "\n"
            "    info:\n"
            "    *  info register-log: print the register/unregister log\n"
            "    *  info students <student_id>: print a student's info\n"
            "    *  info hostels <hostel_id>: print a hostel's info\n"
            "    *  info vacant-hostels\n"
            "    *  info vacant-beds <hostel_id> <building_id>: print a hostel's vacant beds\n"
        );
    }
    else if (strcmp(exe, "register") == 0) {
        if (argc < 2) {
            ECHO_CMD_REQUIRES_ARG(exe);
            return;
        }
        if (strcmp(argv[1], "students") == 0) {
            if (argc != 6) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            register_student(hm_atoi_(argv[2]), hm_atoi_(argv[3]),hm_atoi_(argv[4]), hm_atoi_(argv[5]), true);
        } else if (strcmp(argv[1], "hostels") == 0) {
            if (argc != 5) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            register_hostel(hm_atoi_(argv[2]), hm_atoi_(argv[3]), hm_atoi_(argv[4]));
        } else if (strcmp(argv[1], "buildings") == 0) {
            if (argc != 5) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            register_building(hm_atoi_(argv[2]), hm_atoi_(argv[3]), hm_atoi_(argv[4]));
        } else {
            ECHO_UNKNOWN_OPTION(exe, argv[1]);
            return;
        }
    }
    else if (strcmp(exe, "unregister") == 0) {
        if (argc < 2) {
            ECHO_CMD_REQUIRES_ARG(exe);
            return;
        }
        bool is_fail;
        if (strcmp(argv[1], "students") == 0) {
            if (argc != 3) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            is_fail = unregister_student(hm_atoi_(argv[2]));
        } else if (strcmp(argv[1], "hostels") == 0) {
            if (argc != 4) {
               ECHO_CMD_REQUIRES_ARG(exe);
            }
            is_fail = unregister_hostel(hm_atoi_(argv[2]), hm_atoi_(argv[3]));
        } else if (strcmp(argv[1], "buildings") == 0) {
            if (argc != 3) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            is_fail = unregister_building(hm_atoi_(argv[2]));
        } else {
            ECHO_UNKNOWN_OPTION(exe, argv[1]);
            return;
        }
        if (is_fail) {
            fprintf(stderr, "unregister %s %s but not found\n", argv[1], argv[2]);
        }
    }
    else if (strcmp(exe, "edit") == 0) {
        if (argc < 2) {
            ECHO_CMD_REQUIRES_ARG(exe);
            return;
        }
        if (strcmp(argv[1], "students") == 0) {
            if (argc != 6) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t student_id = hm_atoi_(argv[2]);
            size_t hostel_id = hm_atoi_(argv[3]);
            size_t building_id = hm_atoi_(argv[4]);
            size_t bed_id = hm_atoi_(argv[5]);
            if (edit_student(student_id, hostel_id, building_id, bed_id)) {
                fprintf(stderr, "fail due to student %s not found\n", argv[2]);
            }
        } else if (strcmp(argv[1], "hostels") == 0) {
            if (argc != 5) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t hostel_id = hm_atoi_(argv[2]);
            size_t building_id = hm_atoi_(argv[3]);
            size_t bed_num = hm_atoi_(argv[4]);
            if (edit_hostel(hostel_id, building_id, bed_num)) {
                fprintf(stderr, "fail due to hostel %s not found\n", argv[2]);
            }
        } else if (strcmp(argv[1], "buildings") == 0) {
            if (argc != 5) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t building_id = hm_atoi_(argv[2]);
            size_t hostel_num = hm_atoi_(argv[3]);
            size_t is_male = hm_atoi_(argv[4]);
            if (edit_building(building_id, hostel_num, is_male)) {
                fprintf(stderr, "fail due to building %s not found\n", argv[2]);
                return;
            }
        } else {
            ECHO_UNKNOWN_OPTION(exe, argv[1]);
            return;
        }
    }
    else if (strcmp(exe, "list") == 0) {
        if (argc != 2) {
            ECHO_CMD_REQUIRES_ARG(exe);
            return;
        }
        if (strcmp(argv[1], "students") == 0) {
            list_students();
        } else if (strcmp(argv[1], "hostels") == 0) {
            list_hostels();
        } else if (strcmp(argv[1], "buildings") == 0) {
            list_buildings();
        } else {
            ECHO_UNKNOWN_OPTION(exe, argv[1]);
            return;
        }
    }
    else if (strcmp(exe, "info") == 0) {
        if (argc < 2) {
            ECHO_CMD_REQUIRES_ARG(exe);
            return;
        }
        if (strcmp(argv[1], "register-log") == 0) {
            info_register_log();
        }
        else if (strcmp(argv[1], "students") == 0) {
            if (argc != 3) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t student_id = hm_atoi_(argv[2]);
            Student *student = get_student(student_id);
            if (student == NULL) {
                fprintf(stderr, "Student %zu not found\n", student_id);
            }
            print_student(student);
        }
        else if (strcmp(argv[1], "hostels") == 0) {
            if (argc != 4) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t hostel_id = hm_atoi_(argv[2]);
            size_t building_id = hm_atoi_(argv[3]);
            list_students_in_a_hostel(hostel_id, building_id);
        }
        else if (strcmp(argv[1], "vacant-hostels") == 0) {
            print_vacant_hostels();
        }
        else if (strcmp(argv[1], "cavant-beds") == 0) {
            if (argc != 4) {
                ECHO_CMD_REQUIRES_ARG(exe);
                return;
            }
            size_t hostel_id = hm_atoi_(argv[2]);
            size_t building_id = hm_atoi_(argv[3]);
            Hostel *hostel = get_hostel(hostel_id, building_id);
            if (hostel == NULL) {
                fprintf(stderr, "Hostel not found\n");
                return;
            }
            size_t students = count_students_in_a_hostel(hostel_id);
            if (students == 0) {
                printf("Hostel %zu is empty\n", hostel_id);
            }
            else {
                printf("Hostel %zu has %zu students\n", hostel_id, students);
            }
        }
    }
    else {
        printf("Unknown command: %s\n", exe);
    }

}

HM_private
void free_argv_(void *str) {
    free(*(char**)str);
}

HM_noreturn
HM_private
void start_console(void) {
    HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
    if (hStdin == INVALID_HANDLE_VALUE) {
        HM_throw(InternalError, "get console handle failed! please bug-report");
    }
    if (FlushConsoleInputBuffer(hStdin) == 0) {
        HM_throw(InternalError, "flush console input buffer failed!");
    }

restart:
    printf("hostel manager console $ ");

    char input_buffer[INPUT_BUFFER_SIZE] = {0};
    DWORD cNumRead = 0;
    INPUT_RECORD irInBuf[CONSOLE_CMD_SIZE] = {0};
    unsigned int index = 0;
    bool before_is_space = true;
    while (true) {
        if (!ReadConsoleInputA(hStdin, irInBuf, CONSOLE_CMD_SIZE, &cNumRead)) {
            HM_throw(ReadConsoleInputFail, "read console input failed!");
        }

        for (size_t i = 0; i < cNumRead; ++i) {
            if (irInBuf[i].EventType != KEY_EVENT || !irInBuf[i].Event.KeyEvent.bKeyDown) {
                continue;
            }

            char chr = irInBuf[i].Event.KeyEvent.uChar.AsciiChar;
            if (chr == '\r') {
                putchar('\n');
                if (index != 0) {
                    goto parse_cmd;
                } else {
                    goto restart;
                }
            } else if (chr == ' ') {
                putchar(' ');
                if (!before_is_space) {
                    input_buffer[index++] = ' ';
                    before_is_space = true;
                }
                continue;
            }
            else if (chr == '\b' && index > 0) {
                input_buffer[--index] = 0;
                printf("\b \b");
            }
            else if ('!' <= chr && chr <= '~') {
                putchar(chr);
                input_buffer[index++] = chr;
                before_is_space = false;
            }

            if (index >= INPUT_BUFFER_SIZE) {
                puts("\nInvalidInput: The input is too long!");
                goto restart;
            }
        }
    }

parse_cmd:
    before_is_space = true;
    cvector(char*) argv = NULL;
    cvector_init(argv, 1, free_argv_);
    int argv_index = 0;
    for (int i = 0; i < INPUT_BUFFER_SIZE && input_buffer[i] != 0; ++i) {
        if (input_buffer[i] == ' ') {
            before_is_space = true;
            argv_index = 0;
            continue;
        } else if (before_is_space) {
            cvector_push_back(argv, (char*)calloc(1, CONSOLE_CMD_SIZE));
            before_is_space = false;
        }
        if (argv_index >= CONSOLE_CMD_SIZE) {
            puts("InvalidInput: command too long");
            cvector_free(argv);
            goto restart;
        }
        argv[cvector_size(argv) - 1][argv_index++] = input_buffer[i];
    }
    console_main_(cvector_size(argv), argv);
    cvector_free(argv);
    goto restart;
}

#undef CONSOLE_CMD_SIZE
#undef INPUT_BUFFER_SIZE
